# -*- coding: utf-8 -*-

"""
This game clock manages your time step reliably. It supports independent frame
rate. Frame rate can be limited to an optional maximum. When frame rate is
unlimited frames fire as fast as possible. Interpolation factor is optionally
calculated for fractional stepping and prediction. Though this clock is written
with games in mind it is suitable for other purposes.
"""

# ------------------------------------------------------------------------------
class NegativeDilatationError(Exception):

    def __init__(self, value):
        msg = "Dilatation should be >= 0 but received '%s'" % (value)
        Exception.__init__(self, msg)
        
# ------------------------------------------------------------------------------
class GameClock(object):
    """
    The GameClock class. It will eat all the cpu it can to run your game as
    smooth as possible




    """

    def __init__(self,
                get_ticks,
                update,
                update_interval,
                draw,
                draw_interval=0):
        """
        Constructor.

        :Parameters:
            get_ticks : method
                any method that returns a number which is bigger than the one
                from the last call
            update : callback
                its called after every update_interval, needs to have following
                signature: update(dt)
            update_interval : float or int
                the interval in which the update callback is called, it has to
                be in the same unit as get_ticks returns
            draw : callback
                its called to draw the game, needs following signature:
                draw(interpolation) where interpolation is in [0, 1)
            draw_interval : float or int
                the interval in which it should call draw (can not be bigger
                than update_interval), 0 is as fast as possible
        """
        self._get_ticks = get_ticks
        self._real_time = 0.0

        self._update = update
        self._update_interval = update_interval

        self._draw = draw
        # ensure ups <= fps
        self._draw_interval = draw_interval #if draw_interval < update_interval \
                                                        # else update_interval
        self._next_draw = self._draw_interval

        self._game_time = 0
        self._accumulator = 0
        # TODO: should this factor be tuneable??
        self._max_frame_time = 3 * self._update_interval
        
        self._dilatation = 1.0

    def tick(self):
        """
        Ticks the clock. Call this in you main loop on each frame as fast as
        possible. This will result in heigh cpu usage because it uses busy
        waiting!
        """
        frame_time =  self._get_ticks() - self._real_time
        self._real_time += frame_time

        # this would be the upper limit to prevent the 'spiral of death'
        # this is ~3 times faster than using min(...)
        if frame_time > self._max_frame_time:
            frame_time = self._max_frame_time
            
        # time dilatation
        frame_time *= self._dilatation

        # drive the simulation, integrate in fixed time steps
        self._accumulator += frame_time
        # print '????', self._accumulator
        while self._accumulator >= self._update_interval:
            self._game_time += self._update_interval
            self._update(self._update_interval, self._game_time)
            self._accumulator -= self._update_interval

        # draw using limited fps or unbound
        if self._draw_interval == 0 or self._real_time >= self._next_draw:
            if self._draw_interval > 0:
                self._next_draw += self._draw_interval
                if self._real_time > self._next_draw:
                    num_intervals = round((self._real_time - self._next_draw) / self._draw_interval)
                    self._next_draw += self._draw_interval * num_intervals
            
            alpha = float(self._accumulator) / self._update_interval
            self._draw(alpha)


    @property
    def game_time(self):
        """
        Returns the read-only virtual game time.
        """
        return self._game_time
        
    @property
    def dilatation(self):
        """
        Time dilatation factor.
        
        Meaning:
            > 1      : faster, careful to not use all your CPU
            == 1     : normal time
            0 < x < 1: slow motion
            == 0     : time is stopped
            < 0      : raises an 'NegativeDilatationError'
        """
        return self._dilatation
        
    @dilatation.setter
    def dilatation(self, value):
        """
        Time dilatation, see getter.
        """
        if value < 0:
            raise NegativeDilatationError(value)
        self._dilatation = value


# this is the looping method, the code from self.tick could probably
# be copied into this while loop, but then it can't be tested
#    def run(self):
#        while 1: # TODO: use 'running' variable??
#            self.tick()

# ------------------------------------------------------------------------------

class DebugGameClock(object):
    """
    
    """

    def __init__(self,
                get_ticks,
                update,
                update_interval,
                draw,
                draw_interval=0,
                history=[]):
        """
        Constructor.

        :Parameters:
            get_ticks : method
                any method that returns a number which is bigger than the one
                from the last call
            update : callback
                its called after every update_interval, needs to have following
                signature: update(dt)
            update_interval : float or int
                the interval in which the update callback is called, it has to
                be in the same unit as get_ticks returns
            draw : callback
                its called to draw the game, needs following signature:
                draw(interpolation) where interpolation is in [0, 1)
            draw_interval : float or int
                the interval in which it should call draw (can not be bigger
                than update_interval), 0 is as fast as possible
            history : list
                the list where the entries are appended
        """
        # -- protected --
        self._orig_get_ticks = get_ticks
        self._orig_update = update
        self._orig_update_interval = update_interval
        self._orig_draw = draw
        self._orig_draw_interval = draw_interval
        self._gameclock = None
        self._inititalized = False
        self._real_time = 0
        self._line = []
        
        # -- public --
        self.history = history

    def _init(self):
        """
        """
        self._gameclock = GameClock(self._get_ticks,
                                    self._update,
                                    self._orig_update_interval,
                                    self._draw,
                                    self._orig_draw_interval)
        self._inititalized = True

    def tick(self):
        """
        """
        if not self._inititalized:
            self._init()
        self._gameclock.tick()

        self._line.append(self._gameclock._accumulator)
        self._line.append(self._gameclock._game_time)
        self._line.append(self._gameclock._next_draw)
        if self._frame_time: self.history.append(self._line)

    def _get_ticks(self):
        """
        """
        now = self._orig_get_ticks()
        self._frame_time = now - self._real_time
        self._real_time = now
        self._line = ['      ', self._frame_time, self._real_time]
        return now

    def _update(self, dt):
        """
        dt is the delta time since last udpate call
        """
        self.history.append('update')
        self._orig_update(dt)

    def _draw(self, alpha):
        """
        alpha is the interpolation factor in the range [0, 1)
        """
        self._line.append(alpha)
        self._line[0] = 'draw  '
        self._orig_draw(alpha)

    @property
    def game_time(self):
        if self._gameclock:
            return self._gameclock.game_time
        return 0

# ------------------------------------------------------------------------------
